Rails+iOS应用Azure Active Directory SSO认证对接需求
Alright, let's walk through how to connect your iOS app's Azure AD login flow with your Rails backend to handle user authentication, user record management, and session setup. I've broken this down into clear, actionable steps that tie both ends together seamlessly:
Assuming your iOS app already uses MSAL (or another Azure AD auth library) to get tokens, here's how to send them to your Rails backend:
- First, grab the access token from your login result (MSAL gives you an
MSALResultobject with this). You can also pull basic user info like email/name from the associatedMSALAccountto pass along as optional context. - Send a POST request to a dedicated Rails auth endpoint (e.g.,
/api/auth/azure_ad), putting the access token in theAuthorizationheader using theBearerscheme. Here's a quick Swift example:
guard let accessToken = msalResult.accessToken, let account = msalResult.account else { return } let authUrl = URL(string: "https://your-rails-app-domain.com/api/auth/azure_ad")! var request = URLRequest(url: authUrl) request.httpMethod = "POST" request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization") // Optional: Send basic user info to avoid re-parsing it from the token later let userPayload = [ "email": account.username, "full_name": account.name ] request.httpBody = try? JSONSerialization.data(withJSONObject: userPayload) // Execute the request and handle the server response let task = URLSession.shared.dataTask(with: request) { data, response, error in if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 { // Success! Store any session tokens/cookies from the response for future requests } else { // Handle errors (e.g., invalid token, server issue) } } task.resume()
Now let's handle the token validation, user lookup/creation, and Devise session setup on the Rails side:
2.1 验证Azure AD令牌的有效性
First, you'll need to validate the token's signature, issuer, and audience to make sure it's legitimate. You can use the jwt gem for this, and cache Azure AD's public keys to avoid repeated network calls. Here's how to implement this in a controller:
# app/controllers/api/auth_controller.rb class Api::AuthController < ApplicationController skip_before_action :authenticate_user!, only: [:azure_ad_login] def azure_ad_login # Extract the token from the Authorization header token = request.headers['Authorization']&.split('Bearer ')&.last return render json: { error: "Missing auth token" }, status: :unauthorized unless token begin # Fetch and cache Azure AD's public keys (do this once, not on every request!) azure_ad_keys = JSON.parse(URI.open("https://login.microsoftonline.com/your-tenant-id/discovery/v2.0/keys").read) public_key = JWT::JWK::Set.new(azure_ad_keys).find do |jwk| JWT.decode(token, nil, false, { algorithm: "RS256" })[0]["kid"] == jwk["kid"] end.to_key # Decode and validate the token decoded_token = JWT.decode( token, public_key, true, algorithm: "RS256", iss: "https://login.microsoftonline.com/your-tenant-id/v2.0", aud: "your-azure-ad-client-id", # This should match your Azure AD app's client ID verify_iss: true, verify_aud: true ) user_data = decoded_token[0] rescue JWT::DecodeError, URI::Error => e return render json: { error: "Invalid or expired token" }, status: :unauthorized end
2.2 查找或创建本地用户
Use a unique identifier from the Azure AD token (like the oid field, which is the user's immutable Azure AD object ID) to find or create a local Rails user:
# Use Azure AD's oid as the unique identifier (more reliable than email) azure_ad_oid = user_data["oid"] user = User.find_or_create_by(azure_ad_oid: azure_ad_oid) do |new_user| new_user.email = user_data["email"] new_user.name = user_data["name"] # Devise requires a password; generate a random one since users won't use local auth new_user.password = Devise.friendly_token.first(16) end
2.3 设置Devise的current_user
Finally, sign the user in to set up the Devise session. The approach depends on whether you're building a traditional cookie-based app or an API:
# For cookie-based web apps (if iOS uses a WKWebView): sign_in(user, scope: :user) render json: { message: "Login successful", user: user }, status: :ok # For API apps (using Devise Token Auth): # user.generate_auth_token! # user.save! # render json: { user: user, auth_token: user.authentication_token }, status: :ok end end end
- iOS Side: If using cookies, URLSession will automatically persist the session cookie for future requests. If using token auth, include the Rails-generated auth token in the
Authorizationheader for every protected request. - Rails Side: Protect your resources with Devise's
authenticate_user!filter to ensure only logged-in users can access them:
class Api::ProtectedResourcesController < ApplicationController before_action :authenticate_user! def index # Return your protected data here render json: { resources: YourModel.all } end end
- Double-check your Azure AD app configuration: Make sure your Rails server's domain is allowed in the app's redirect URIs (if using implicit flow) and that the audience (
aud) in your token validation matches your Azure AD client ID. - Cache Azure AD's public keys! Fetching them on every request will slow down your backend. Use Rails.cache to store them for a few hours.
- Handle token expiration on iOS: If your Rails server returns a 401 Unauthorized response, trigger MSAL's token refresh flow to get a new access token before retrying the request.
内容的提问来源于stack exchange,提问作者Matt Long

